package com.disv.support.encrypt;

import com.disv.support.encrypt.domain.EncryptData;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Enumeration;
import java.util.StringTokenizer;


/**
 * 私钥：用于对固定密钥密文解密、加签
 *
 * @author caojian.cn@gmail.com
 * @date 2021/7/1
 */

@Slf4j
@Getter
@Setter
public class CertificatePrivateKey {
    protected PrivateKey privateKey;
    protected String theName;
    /**
     * 证书id
     */
    protected String certId;

    public CertificatePrivateKey(String pfxFile, String password) throws CertificateException {
        ClassPathResource classPathResource = new ClassPathResource(pfxFile);
        if (!classPathResource.exists()) {
            throw new CertificateException("私钥文件不存在");
        }

        try (InputStream fis = classPathResource.getInputStream()) {
            KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
            keyStore.load(fis, password.toCharArray());
            fis.close();
            Enumeration<String> aliases = keyStore.aliases();
            String alias = aliases.nextElement();
            this.privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray());
            //给证书ID赋值
            this.certId = getCertIdIdByStore(keyStore);
            log.info("pfxFile:" + pfxFile);
            log.info("证书ID_certId:" + this.certId);
        } catch (KeyStoreException keyStoreException) {
            throw new CertificateException("密钥加载失败", keyStoreException);
        } catch (NoSuchProviderException providerException) {
            throw new CertificateException("算法模块加载失败", providerException);
        } catch (NoSuchAlgorithmException algorithmException) {
            throw new CertificateException("证书算法不支持", algorithmException);
        } catch (UnrecoverableKeyException unrecoverableKeyException) {
            throw new CertificateException("证书加载失败，请检查口令是否正确", unrecoverableKeyException);
        } catch (IOException ioException) {
            throw new CertificateException("证书文件读取失败", ioException);
        }
    }


    /**
     * 取得certId
     */
    private static String getCertIdIdByStore(KeyStore keyStore) {
        Enumeration<String> aliasEnum;
        try {
            aliasEnum = keyStore.aliases();
            String keyAlias = null;
            if (aliasEnum.hasMoreElements()) {
                keyAlias = aliasEnum.nextElement();
            }
            X509Certificate cert = (X509Certificate) keyStore
                    .getCertificate(keyAlias);
            return cert.getSerialNumber().toString();
        } catch (KeyStoreException e) {
            log.error("getCertIdIdByStore Error", e);
            return null;
        }
    }

    /**
     * 加签
     */
    public String sign(String plaintext) throws Exception {
        //
        String algName = getSignAlgName();
        log.info("签名算法:" + algName);
        //得到签名对象
        Signature engine = Signature.getInstance(algName, "BC");
        //加载私钥
        engine.initSign(this.privateKey);
        //编码方式
        engine.update(plaintext.getBytes(StandardCharsets.UTF_8));

        return Base64.getEncoder().encodeToString(engine.sign());
    }


    /**
     * 解密
     */
    public String decryptForKeyAndText(String cipherText) throws Exception {
        if (cipherText == null) {
            throw new Exception("密文为空");
        }
        // 将密文分隔为密钥和报文体两部分
        StringTokenizer tokenizer = new StringTokenizer(cipherText, ".");
        if (tokenizer.countTokens() == 0) {
            throw new InvalidParameterException("decryptDigitalEnvelope cipherText format error");
        }
        String aesKeyString = tokenizer.nextToken();
        String messageString = tokenizer.nextToken();

        // 使用私钥解密密钥部分
        // 密钥部分的前16字节是IV，后32字节是SM4或AES密钥
        String keyAlgName = getKeyAlgName();
        log.info("解密过程，非对称密钥算法:" + keyAlgName);
        String bodyText = "";
        Cipher keyCipher = Cipher.getInstance(keyAlgName, "BC");
        //使用私钥对密钥密文进行解密
        keyCipher.init(Cipher.DECRYPT_MODE, privateKey);
        //密钥明文
        byte[] encryptedBytes = keyCipher.doFinal(Base64.getDecoder().decode(aesKeyString));
        int keySize = getKeySize();
        byte[] iv = new byte[16];
        byte[] keyBytes = new byte[keySize];
        System.arraycopy(encryptedBytes, 0, iv, 0, 16);
        System.arraycopy(encryptedBytes, 16, keyBytes, 0, keySize);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        //固定密钥算法
        String keySpecName = getKeySpecName();
        log.info("解密过程，固定密钥算法:" + keySpecName);
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, keySpecName);
        //固定密钥明文
        String solidKeyText = new String(encryptedBytes);
        // log.info("解密过程，固定密钥明文：" + solidKeyText);
        // 使用解密的密钥和IV解密报文
        String msgAlgName = getMsgAlgName();
        log.info("解密过程，固定密钥算法参数:" + msgAlgName);
        Cipher cipher = Cipher.getInstance(msgAlgName, "BC");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
        bodyText = new String(cipher.doFinal(Base64.getDecoder().decode(messageString)));
        log.info("解密后：" + bodyText);
        return bodyText;
    }

    public String decryptBodyData(EncryptData encryptData) throws Exception {
        String encryptKeyBase64 = encryptData.getKey();
        String encryptMethod = encryptData.getEncryptMethod();// TODO   -目前未用起来
        String encryptDataBase64 = encryptData.getData();

        // 报文明文
        String data = "";
        try {
            String keyAlgName = getKeyAlgName();
            log.info("解密过程，密钥的加密算法(非入参取值):{}", keyAlgName);
            Cipher keyCipher = Cipher.getInstance(keyAlgName, "BC");
            //使用私钥对密钥密文进行解密
            keyCipher.init(Cipher.DECRYPT_MODE, privateKey);
            //密钥明文
            byte[] encryptedBytes = keyCipher.doFinal(Base64.getDecoder().decode(encryptKeyBase64));
            int keySize = getKeySize();
            byte[] iv = new byte[16];
            byte[] keyBytes = new byte[keySize];
            System.arraycopy(encryptedBytes, 0, iv, 0, 16);
            System.arraycopy(encryptedBytes, 16, keyBytes, 0, keySize);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
            //固定密钥算法
            String keySpecName = getKeySpecName();
            log.info("解密过程，固定密钥算法:" + keySpecName);
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, keySpecName);
            //固定密钥明文
            String solidKeyText = new String(encryptedBytes);
            // log.info("解密过程，固定密钥明文：" + solidKeyText);
            // 使用解密的密钥和IV解密报文
            String msgAlgName = getMsgAlgName();
            log.info("解密过程，固定密钥算法参数:" + msgAlgName);
            Cipher cipher = Cipher.getInstance(msgAlgName, "BC");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
            data = new String(cipher.doFinal(Base64.getDecoder().decode(encryptDataBase64)));
        } catch (Exception e) {
            log.info("解密过程，====>异常:", e);
            //throw new ServiceException(PlatResultCode.COMMON_S_BODY_DECRYPT_ERROR);
            throw e;
        }

        log.info("解密后data:{}", data);
        return data;
    }

    private String getSignAlgName() {
        return this.privateKey.getAlgorithm().equals("RSA") ? "SHA256WITHRSA" : "SM3WITHSM2";
    }

    private String getKeyAlgName() {
        return this.privateKey.getAlgorithm().equals("RSA") ? "RSA/NONE/PKCS1Padding" : "SM2";
        //return this.privateKey.getAlgorithm().equals("RSA") ? "RSA" : "SM2";

    }

    private String getMsgAlgName() {
        return this.privateKey.getAlgorithm().equals("RSA") ? "AES/CBC/PKCS5Padding" : "SM4/CBC/PKCS5Padding";
    }

    private String getKeySpecName() {
        return this.privateKey.getAlgorithm().equals("RSA") ? "AES" : "SM4";
    }

    private int getKeySize() {
        return this.privateKey.getAlgorithm().equals("RSA") ? 32 : 16;
    }

    public String getTheName() {
        return theName;
    }

    public void setTheName(String theName) {
        this.theName = theName;
    }
}
